package com.leo.boot.feature.merge2;

import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;

import static com.leo.boot.feature.merge2.ClassMerging.Scope.EXPLICIT;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.reflect.FieldUtils.getAllFieldsList;
import static org.apache.commons.lang3.reflect.FieldUtils.getFieldsListWithAnnotation;
import static org.apache.commons.lang3.reflect.MethodUtils.getMatchingMethod;

@Slf4j
public class ClassDelegate {

    ClassMerging classMerging;

    @Getter
    List<FieldDelegate> fieldDelegates;

    Pair<Class<?>, Method> pre;

    Pair<Class<?>, Method> post;

    @SneakyThrows
    ClassDelegate(Class<?> clazz) {
        this.classMerging = clazz.getAnnotation(ClassMerging.class);
        if (this.classMerging == null) {
            this.fieldDelegates = initFieldDelegates(clazz, false);
            return;
        }

        if (this.classMerging.scope() == EXPLICIT) {
            this.fieldDelegates = initFieldDelegates(clazz, false);
        } else {
            this.fieldDelegates = initFieldDelegates(clazz, true);
        }
        if (isNotBlank(classMerging.pre())) {
            this.pre = ImmutablePair.of(clazz, getMatchingMethod(clazz, classMerging.pre(), clazz));
        }
        if (isNotBlank(classMerging.post())) {
            this.post = ImmutablePair.of(clazz, getMatchingMethod(clazz, classMerging.post(), clazz));
        }
    }

    private static List<FieldDelegate> initFieldDelegates(Class<?> clazz, boolean explicit) {
        List<Field> fields = explicit ? getFieldsListWithAnnotation(clazz, FieldMerging.class) : getAllFieldsList(clazz);
        return fields.stream().map(FieldDelegate::new).filter(fd -> !fd.ignore()).collect(toList());
    }

    @SneakyThrows
    public void pre(Object obj) {
        if (pre == null) {
            return;
        }
        pre.getRight().invoke(pre.getLeft(), obj);
    }

    @SneakyThrows
    public void post(Object obj) {
        if (post == null) {
            return;
        }
        post.getRight().invoke(post.getLeft(), obj);
    }
}
